#/usr/bin/python
# -*- coding: utf-8 -*-
import re
import sys
sys.path.append('../../../icafe')
import parameters

## Parser abstract class.
class TemplateParser():
    
    def __init__(self):
        pass

    def read(self):
        pass
    
    def write(self):
        pass
    
    def buildModel(self):
        pass
    
    def buildData(self):
        pass


class Parser(TemplateParser):
    
    def _init__(self,filename):
        
        self.filename = filename
        
        self.reader = AbaqusReader()
        self.writer = AbaqusWriter()

    def read(self):
        
        self.reader.read()
    
    def write(self):
        
        self.writer.write()
        
    def buildModel(self):
        pass
    
    def buildData(self):
        pass

class AbaqusReader():

    MAX_ABQ_FIELDS = 16
    def __init__(self, fname):
        self.omesh = None
        self.o_st_mngr = None
        self.filename = fname
        self.file = open(self.filename, 'rb')
        self.file_list = [ self.file ]
        self.current_step = None
        self.current_line_num = 0
        self.end_of_file_reached = False

        self.prop_name_to_id = {}
        
        self.vals = [ 0 ] * AbaqusReader.MAX_ABQ_FIELDS

    def __del__(self):
        map(file.close, self.file_list)

    def del_unused_after_input(self):
        del self.prop_name_to_id
        self.prop_name_to_id = None

    abaqus_keyword_pattern = re.compile('^\*[A-Za-z ]+')
    abaqus_keyword_pattern_fast = re.compile('^\*[A-Za-z]')
    
    @staticmethod
    def l_is_keyword(line):

        #m = AbaqusReader.abaqus_keyword_pattern_fast.match(line)
        #if not m: return None
        if line and line[0] != '*': return None

        m = AbaqusReader.abaqus_keyword_pattern.match(line)
        if not m: return None
        return m.group(0).upper()

    @staticmethod
    def l_is_comment(line):

        if line[:2] == '**':
            return True
        else:
            return False


    param_pairs_pattern = re.compile('([A-Za-z][\w ]*)(=([\w;\./ -]*)|)')
    @staticmethod
    def decode_keyword_line(line):
        #k, p = decode_keyword_line("*ELEMENT, TYPE=LALALA, V=wwwww, V2=0.2, WW")
        # FIXME read parameters spanning lines
        keyword = AbaqusReader.l_is_keyword(line)

        param_pairs = line.split(',')

        parameters = {}
        for param_pair in param_pairs[1:]:
            param_pair = param_pair.strip()
            m = AbaqusReader.param_pairs_pattern.match(param_pair)
            if m:
                if m.group(3):
                    parameters[m.group(1).strip().upper()] = \
                            m.group(3).strip()
                else:
                    parameters[m.group(1).strip().upper()] = None


        return keyword, parameters
            

    def decode_data_line(self, line, min_num_vals = MAX_ABQ_FIELDS):

        vals = line.split(',')
        nfields = len(vals)
        
        for i, val in enumerate(vals):

            
            try:
                self.vals[i] = eval(val)
            except NameError:
                self.vals[i] = val
            except SyntaxError:
                self.vals[i] = val
            
        return nfields

    def readline(self):
        self.current_line = self.file.readline()
        # skip comments....
        while AbaqusReader.l_is_comment(self.current_line):
            self.current_line = self.file.readline()
        if self.current_line:
            self.current_line = self.current_line.rstrip()
            self.current_line_num = self.current_line_num + 1
            return True
        else:
            file = self.file_list.pop()
            file.close()
            if len(self.file_list) == 0:
                self.end_of_file_reached = True
                return False
            self.file = self.file_list[-1]
            return True


    def read_include(self, keyword, parameters):
        keyword, parameters = self.decode_keyword_line(self.current_line)
        if parameters.has_key('INPUT'):
            filename = parameters['INPUT']

            if not os.path.isabs(self.filename):
                filename = os.path.join(os.path.dirname(self.filename),filename)
            if os.path.isfile(filename):
                file = open(filename, 'rb')
                if file:
                    self.file_list.append(file)
                    self.file = file
            else:
                print '*INCLUDE %s is missing' % filename

        return 0

        
    def dummy_read_keyword(self):
        while self.readline():
            if AbaqusReader.l_is_keyword(self.current_line):
                return 1

            #print self.current_line


        return 0

    def prop_id_from_name(self, prop_name):
        if not self.prop_name_to_id.has_key(prop_name):
            self.prop_name_to_id[prop_name] = \
                len(self.prop_name_to_id) + 1
        return self.prop_name_to_id[prop_name]

    def read_nodes(self, keyword, parameters):
        
        # ar.current_line contains *NODE.
        n_coo = self.omesh.nodesCoo
        n_map = self.omesh.nodesMap
        
        next_node_codeid = len(n_coo)
        
        while self.readline():

            if AbaqusReader.l_is_keyword(self.current_line):
                return 1

            self.decode_data_line(self.current_line, 4)
            id = self.vals[0]
            
            if id in n_map.keys():
                print "WARNING: node ", id, " already exists."
                n_coo[n_map["U2C"][id]] = self.vals[1:4]
            else:   
                n_map["U2C"][id] = next_node_codeid
                n_map["C2U"].append(id)
                next_node_codeid += 1
                n_coo.append(self.vals[1:4])
                
#                n_arr[id] = Node(id, self.fvals[1], self.fvals[2], self.fvals[3])
#            else:
#                print "Node id: %d already exists" % id
            
        return 0

    def read_shells(self, keyword, parameters, is_tria):
        
        # get elements parameters:
        if parameters.has_key('ELSET'):
            _elset = parameters['ELSET']
        else: _elset = None
        if parameters.has_key('TYPE'):
            _type = parameters['TYPE']
        else: _type = None
        
        # load element containers
        el_map = self.omesh.elementsMap
        el_nodes = self.omesh.elementsNodes
        el_type = self.omesh.elementsType
        n_map = self.omesh.nodesMap
        
        next_elem_codeid = len(el_map["C2U"])
        
        while self.readline():

            if AbaqusReader.l_is_keyword(self.current_line):
                return 1

            self.decode_data_line(self.current_line, 5)

            _id = self.vals[0]
            n1 = self.vals[1]
            n2 = self.vals[2]
            n3 = self.vals[3]
            n4 = self.vals[4]
            if is_tria: n4 = 0


            if _id in el_map.keys():
                print "WARNING: element ", _id, " already exists."
                if is_tria:
                    el_nodes[el_map["U2C"][_id]] = [n_map["U2C"][n1],n_map["U2C"][n2],n_map["U2C"][n3]]
                    el_type[el_map["U2C"][_id]] = _type
                else:
                    el_nodes[el_map["U2C"][_id]] = [n_map["U2C"][n1],n_map["U2C"][n2],n_map["U2C"][n3],n_map["U2C"][n4]]
                    el_type[el_map["U2C"][_id]] = _type
            else:
                el_map["U2C"][_id] = next_elem_codeid
                el_map["C2U"].append(_id)
                if is_tria:
                    el_nodes.append([n_map["U2C"][n1],n_map["U2C"][n2],n_map["U2C"][n3]])
                else:
                    el_nodes.append([n_map["U2C"][n1],n_map["U2C"][n2],n_map["U2C"][n3],n_map["U2C"][n4]])
                el_type.append(_type)
#                el_prop.append()
                
                next_elem_codeid += 1
                   
#            if id > 0 and n1 > 0 and n2 > 0 and n3 > 0 and n4 >= 0:
#                if not sh_arr.has_key(id):
#                    sh_arr[id] = Shell(id, n1, n2, n3, n4, prop)
#                else:
#                    print "Shell id: %d already exists" % id

        return 0

    def read_shells3(self, keyword, parameters):
        return self.read_shells(keyword, parameters, True)
    
    def read_shells4(self, keyword, parameters):
        return self.read_shells(keyword, parameters, False)
    
    def read_bars(self, keyword, parameters):
        
        # get elements parameters:
        if parameters.has_key('ELSET'):
            _elset = parameters['ELSET']
        else: _elset = None
        if parameters.has_key('TYPE'):
            _type = parameters['TYPE']
        else: _type = None
        
        # load element containers
        el_map = self.omesh.elementsMap
        el_nodes = self.omesh.elementsNodes
        el_type = self.omesh.elementsType
        n_map = self.omesh.nodesMap
        
        next_elem_codeid = len(el_map["C2U"])
        
        while self.readline():

            if AbaqusReader.l_is_keyword(self.current_line):
                return 1

            self.decode_data_line(self.current_line, 3)

            _id = self.vals[0]
            n1 = self.vals[1]
            n2 = self.vals[2]


            if _id in el_map.keys():
                print "WARNING: element ", _id, " already exists."
                el_nodes[el_map["U2C"][_id]] = [n_map["U2C"][n1],n_map["U2C"][n2]]
                el_type[el_map["U2C"][_id]] = _type
            else:
                el_map["U2C"][_id] = next_elem_codeid
                el_map["C2U"].append(_id)
                el_nodes.append([n_map["U2C"][n1],n_map["U2C"][n2]])
                el_type.append(_type)
#                el_prop.append()
                
                next_elem_codeid += 1
        
#        if parameters.has_key('ELSET'):
#            prop = self.prop_id_from_name(parameters['ELSET'])
#        else: prop = None
#
#        bar_arr = entities_array('BAR')
#        while self.readline():
#
#            if AbaqusReader.l_is_keyword(self.current_line):
#                return 1
#
#            self.decode_data_line(self.current_line, 3)
#
#            id = self.ivals[0]
#            n1 = self.ivals[1]
#            n2 = self.ivals[2]
#
#            if id > 0 and n1 > 0 and n2 > 0:
#                if not bar_arr.has_key(id):
#                    bar_arr[id] = Bar(id, n1, n2, prop)
#                else:
#                    print "Bar id: %d already exists" % id

        return 0

    def read_section(self, keyword, parameters):
        
        # section parameters:
        try:
            elset = parameters['ELSET']
        except:
            elset = None
        try:
            _id = parameters['NAME']
        except:
            _id = 0 #self.prop_id_from_name(parameters['ELSET'])
        try:
            matid = parameters['MATERIAL']
        except:
            matid = "UNDEFINED"
        
        # ... #
        
#        id = self.prop_id_from_name(parameters['ELSET'])
#            sec = Section(id, prop, 
#                self.properties_to_element_types[keyword])
#            sec_arr = entities_array('PROP')
        sections = self.omesh.sections
        properties = self.omesh.properties
        
        props = self.sections_to_properties[keyword]
        
        if not sections.has_key(_id):
            sections[_id] = {"TYPE": props[0]}
        
                #print 'Section: %s already exists' % \
                #    parameters['ELSET']
          
        self.readline()
            
        if self.l_is_keyword(self.current_line):
            return 1
            
        self.decode_data_line(self.current_line, len(props)-1)
        
        for i,prop in enumerate(props[1:]):
            sections[_id][prop] =  self.vals[i]
        
        properties.append({"SECTION": _id,"MATERIAL": matid, "ELSET": [elset]})
            
        return 0

    def read_material(self, keywords, parameters):
        
        if keywords == "*MATERIAL":
            try:
                name = parameters["NAME"]
            except:
                name = "UNDEFINED"
                
            self.current_material = name  
        
        # MATERIAL TYPES:      
        if keywords == "*ELASTIC":
            try:
                _type = parameters["TYPE"]
            except:
                _type = "ISOTROPIC"
        
        # TO BE ADDED: other type of material properties
               
        self.readline()
        
        if self.l_is_keyword(self.current_line):
            return 1
        
        name = self.current_material
        materials = self.omesh.materials
        
        props = AbaqusReader.elastic_type_to_properties[_type]
        
        self.decode_data_line(self.current_line, len(props)-1)
        
        materials[name] = {"TYPE": _type}
        
        for i, prop in enumerate(props):
            materials[name][prop] = self.vals[i]
            
        
        return 0
    
    def read_boundary_conditions(self, keywords, parameters):  
        
        bc = self.omesh.bC
        
        
        steps_map = self.o_st_mngr.stepsMap
        steps = self.o_st_mngr.steps
        if self.current_step:
            # edit current step:
            step = steps[steps_map["U2C"][self.current_step]]
            step['BC'].append(len(bc))
        else:
            steps_map["U2C"]["INITIAL"]=0
            steps_map["C2U"].append("INITIAL")
            steps.append({"BC":[len(bc)]})
            self.current_step = "INITIAL"
            
        # add boundary condition to mesh
        bc.append({})
        
        while self.readline():
        
            if AbaqusReader.l_is_keyword(self.current_line):
                return 1
        
            nfields = self.decode_data_line(self.current_line)
            
            props = AbaqusReader.boundary_condition_properties[nfields]
            
            for i,prop in enumerate(props):
                if prop == 'DOF':
                    try:
                        bc[-1][prop] = range(bc[-1][prop],self.vals[i]+1)
                    except:
                        bc[-1][prop] = [self.vals[i]]
                else:
                    bc[-1][prop] = self.vals[i]
            
        return 0

    def read_elset(self, keywords, parameters):
        
        try: 
            name = parameters['ELSET']
        except:
            name = "UNDEFINED"
            
        elset = self.omesh.elemSet
        elset_map = self.omesh.elemSetMap
        el_map = self.omesh.elementsMap
        
        if not elset_map["U2C"].has_key(name):
            elset_map["U2C"][name] = len(elset)
            elset_map["C2U"].append(name)
            elset.append([])
         
        while self.readline():
        
            if AbaqusReader.l_is_keyword(self.current_line):
                return 1
            
            nfields = self.decode_data_line(self.current_line)
            
            elset[elset_map["U2C"][name]].extend([el_map["U2C"][self.vals[e]] for e in range(0,nfields)])
        
        
        return 0
    
    def read_nset(self, keywords, parameters):
        
        try: 
            name = parameters['NSET']
        except:
            name = "UNDEFINED"
         
        nset = self.omesh.nodeSet
        nset_map = self.omesh.nodeSetMap
        
        if not nset_map["U2C"].has_key(name):
            nset_map["U2C"][name] = len(nset)
            nset_map["C2U"].append(name)
            nset.append([])
            
        while self.readline():
            
            if AbaqusReader.l_is_keyword(self.current_line):
                return 1
            
            nfields = self.decode_data_line(self.current_line)
            
            nset[nset_map["U2C"][name]].extend(self.vals[0:nfields])
        
        
        return 0
    
    def read_step(self, keywords, parameters):
        
        if keywords == "*STEP":
            try:
                name = parameters["NAME"]
            except:
                name = "UNDEFINED"
                
            self.current_step = name  
        
        # STEP TYPE:      
        if keywords == "*STATIC":
            _type = "STATIC"
        
        # TO BE ADDED: other type of material properties
               
        self.readline()
        
        if self.l_is_keyword(self.current_line):
            return 1
        
        name = self.current_step
        steps_map = self.o_st_mngr.stepsMap
        steps = self.o_st_mngr.steps
        
        self.decode_data_line(self.current_line)
        
        if not steps_map["U2C"].has_key(name):
            steps_map["U2C"][name] = len(steps)
            steps_map["C2U"].append(name)
            steps.append({})
        
        props = AbaqusReader.step_type_to_properties[_type]    
            
        for i, prop in enumerate(props):
            steps[steps_map["U2C"][name]][prop] = self.vals[i]
        
        steps[steps_map["U2C"][name]]['BC'] = []
        steps[steps_map["U2C"][name]]['LC'] = []
        
           
        return 0
     
    def read_keyword(self, keyword):
        keyword, parameters = self.decode_keyword_line(self.current_line)
        if self.keywords_library_read_fun.has_key(keyword):
            print 'reading %s' % keyword
            reader = self.keywords_library_read_fun[keyword]
            try:
                return reader(self, keyword, parameters)
            except TypeError:
                for p, pval, rfun in reader:
                    if parameters.has_key(p) and\
                       parameters[p] == pval:
                        return rfun(self, keyword, parameters)
                print 'skipping, %s %s=%s' % \
                    (keyword, p, parameters[p])
        else:
            print 'skipping %s' % keyword
            return self.dummy_read_keyword()

    #======================================================================
    keywords_library_read_fun = { 
        '*NODE' : read_nodes,
        '*INCLUDE' : read_include,
        '*ELEMENT' : ( ( 'TYPE', 'S4', read_shells4 ), 
                       ( 'TYPE', 'S4R', read_shells4 ),
                       ( 'TYPE', 'S4RS', read_shells4 ),
                       ( 'TYPE', 'S3', read_shells3 ),
                       ( 'TYPE', 'S3R', read_shells3 ),
                       ( 'TYPE', 'S3RS', read_shells3 ),
                       ( 'TYPE', 'M3D4', read_shells4 ),
                       ( 'TYPE', 'M3D4R', read_shells4 ),
                       ( 'TYPE', 'M3D3', read_shells3 ),

                       ( 'TYPE', 'S8R', read_shells4 ),
                       ( 'TYPE', 'S8R5', read_shells4 ),
                       ( 'TYPE', 'M3D8', read_shells4 ),
                       ( 'TYPE', 'M3D8R', read_shells4 ),
                       ( 'TYPE', 'M3D6', read_shells3 ),

                   # Joke: surfaces are shells!
                       ( 'TYPE', 'SFM3D4', read_shells4 ),
                       ( 'TYPE', 'SFM3D4R', read_shells4 ),
                       ( 'TYPE', 'SFM3D3', read_shells4 ),

                       ( 'TYPE', 'B31', read_bars ),
                       ( 'TYPE', 'CONN3D2', read_bars ),
                 ),
        '*SHELL SECTION' : read_section,
        '*MEMBRANE SECTION' : read_section,
        '*SURFACE SECTION' : read_section,
        '*SOLID SECTION' : read_section,
        '*BEAM SECTION' : read_section,
        '*BEAM GENERAL SECTION' : read_section,
        '*CONNECTOR SECTION' : read_section,
        
        '*MATERIAL' : read_material,
        '*ELASTIC': read_material,
        
        '*BOUNDARY': read_boundary_conditions,
        
        '*ELSET': read_elset,
        '*NSET': read_nset,
        
        '*STEP': read_step,
        '*STATIC': read_step,
    }
    
    step_type_to_properties = {
        'STATIC': ('P1','P2','P3','P4','P5'),
    }
    
    boundary_condition_properties = {
        3: ('NSET', 'DOF', 'DOF'),
        4: ('NSET', 'DOF', 'DOF', 'VALUE'),
    }
    
    elastic_type_to_properties = {
        'ISOTROPIC': ('E','nu'),
        'ORTHOTROPIC':('D1111','D1122','D1133','D2222','D2233','D3333','D1212','D1313','D2323'),
        'ENGINEERING CONSTANTS':('E1','E2','E3','nu12','nu13','nu23','G12','G13','G23'),   
    }
    
    sections_to_properties = { 
        '*SHELL SECTION' : ('SHELL','THICKNESS','INTEGRATION'),
        '*MEMBRANE SECTION' : ('SHELL','THICKNESS','INTEGRATION'),
        '*SURFACE SECTION' : ('SHELL'),
        '*SOLID SECTION' : ('SOLID'), # Trusses also...
        '*BEAM SECTION' : ('BAR'),
        '*BEAM GENERAL SECTION' : ('BAR'),
        '*CONNECTOR SECTION' : ('BAR'),
    }

    def read_file(self):

        st = True
        keyword_line_pending = 0
        while st:
            if not keyword_line_pending:
                st = self.readline()

            keyword_line_pending = 0

            keyword = AbaqusReader.l_is_keyword(self.current_line)
            if keyword:
                keyword_line_pending = self.read_keyword(keyword)

            if self.end_of_file_reached: break
                
            if not keyword_line_pending:
                pass
                #print "Unread line: %d: `%s'" % \
                #    (self.current_line_num, self.current_line)
        
        self.del_unused_after_input()

    def set_mesh(self,MESH):
        
        self.omesh = MESH
        
    def get_mesh(self):
        
        return self.omesh
    
    def set_st_mngr(self,ST_MNGR):
        
        self.o_st_mngr = ST_MNGR
    
    def get_st_mngr(self):
        
        return self.o_st_mngr
    
#    sup_keywords_library = { 
#        'NODE': [self.omesh.nodesMap,self.omesh.nodesCoo],
#        'ELEMENT': [self.omesh.elementsMap,self.omesh.elementsNodes,self.omesh.elementsType,self.omesh.elementsProp],
#        'ELSET': [],
#        'NSET': [],
#        'SECTION': [],
#        'MATERIALS': [],
#    }
#
#    #==============================================================================
#    def entities_array(self,keyword):
#        return  self.sup_keywords_library[keyword]



FORMAT_TO_READER = {
            "inp": AbaqusReader,
    }